// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::{Path, PathBuf};

use anyhow::Context;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

use crate::database::local::find_patches;

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PatchConfig {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub extra_entries: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub update_autoconf_config: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct PatchConfigOverrides {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub extra_entries: Option<Vec<String>>,
}

impl PatchConfig {
    pub fn merge_overrides(&mut self, overrides: &PatchConfigOverrides) {
        if let Some(extra_entries) = overrides.extra_entries.as_ref() {
            self.extra_entries = extra_entries.clone();
        }
    }

    pub async fn spec_hash<P: AsRef<Path>>(config: Option<&Self>, hasher: &mut Sha256, base_dir: P) -> anyhow::Result<()> {
        for path in Self::collect_patch_paths(config, base_dir).await? {
            let content = tokio::fs::read_to_string(&path)
               .await
               .context(format!("failed to read patch file '{}'", path.display()))?;
            hasher.update(content.as_bytes());
        }
        Ok(())
    }

    pub async fn collect_patch_paths<P: AsRef<Path>>(config: Option<&Self>, base_dir: P) -> anyhow::Result<Vec<PathBuf>> {
        async fn scan_patch_dir(paths: &mut Vec<PathBuf>, base_dir: &Path, dir: &str) -> anyhow::Result<()> {
            let patches_dir = base_dir.join(dir);
            if !patches_dir.exists() {
                return Ok(());
            }
            
            let mut find_patches = find_patches(&patches_dir).await
                .context(format!("failed to read patches directory '{}'", patches_dir.display()))?;
            while let Some(patch_path) = find_patches.next().await? {
                paths.push(patch_path);
            }
            Ok(())
        }

        let mut paths = Vec::new();
        let base_dir = base_dir.as_ref();
        if let Some(c) = config {
            for dir in c.extra_entries.iter() {
                scan_patch_dir(&mut paths, &base_dir, dir).await?;
            }
        }
        scan_patch_dir(&mut paths, &base_dir, "patches").await?;
        
        paths.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
        Ok(paths)
    }
}
